/************************************************************************
 * Name: Dex Dump Unpacker - Defeat Java packers
 * OS: Android
 * Author: @enovella & guoqiangck
 * Source: https://github.com/enovella/fridroid-unpacker
 * Info: 
    Defeat Java packers via Frida instrumentation
    Use the method OpenMemory or OpenCommon (after Android N) in libart.so/libdexfile.so to get the address of the dex in memory, calculate the size of the dex file, and dump the dex from memory.
*************************************************************************/

'use strict';

function logPrint(log) {
    var theDate = new Date();
    var hour = theDate.getHours();
    var minute = theDate.getMinutes();
    var second = theDate.getSeconds();
    var mSecond = theDate.getMilliseconds()

    hour < 10 ? hour = "0" + hour : hour;
    minute < 10 ? minute = "0" + minute : minute;
    second < 10 ? second = "0" + second : second;
    mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;

    var time = hour + ":" + minute + ":" + second + ":" + mSecond;
    send("[" + time + "] " + log);
}

function getAndroidVersion(){
    var version = 0;

    if(Java.available){
        var version = parseInt(Java.androidVersion);
    }else{
        logPrint("Error: cannot get android version");
    }
    logPrint("[*] Android version: " + version);

    return version;
}

function getFunctionName(){
    var i = 0;
    var functionName = "";

    // Android 4: hook dvmDexFileOpenPartial
    // Android 5: hook OpenMemory
    // after Android 5: hook OpenCommon
    if (g_AndroidOSVersion > 4){ // android 5 and later version
        // OpenCommon is in libdexfile.so in android 10 and later
        var soName = g_AndroidOSVersion >= 10 ? "libdexfile.so" : "libart.so";
        var artExports =  Process.getModuleByName(soName).enumerateExports();
        for(i = 0; i< artExports.length; i++){
            if(artExports[i].name.indexOf("OpenMemory") !== -1){
                functionName = artExports[i].name;
                logPrint("[*] Export index: " + i + " -> "+ functionName);
                break;
            }else if(artExports[i].name.indexOf("OpenCommon") !== -1){
                if (g_AndroidOSVersion >= 10 && artExports[i].name.indexOf("ArtDexFileLoader") !== -1)
                    continue;
                functionName = artExports[i].name;
                logPrint("[*] Export index: " + i + " -> "+ functionName);
                break;
            }
        }
    }else{ //android 4
        var dvmExports =  Process.getModuleByName("libdvm.so").enumerateExports();
        if (dvmExports.length !== 0) {
            for(i = 0; i< dvmExports.length; i++){
                if(dvmExports[i].name.indexOf("dexFileParse") !== -1){
                    functionName = dvmExports[i].name;
                    logPrint("[*] Export index: " + i + " -> "+ functionName);
                    break;
                }
            }
        }else {
            dvmExports = Process.getModuleByName("libart.so").enumerateExports();
            for(i = 0; i< dvmExports.length; i++){
                if(dvmExports[i].name.indexOf("OpenMemory") !== -1){
                    functionName = dvmExports[i].name;
                    logPrint("[*] Export index: " + i + " -> "+ functionName);
                    break;
                }
            }
        }
    }
    return functionName;
}

function getg_processName(){
    var g_processName = "";

    var libc = Process.getModuleByName("libc.so");

    var fopenPtr = libc.findExportByName("fopen");
    var fgetsPtr = libc.findExportByName("fgets");
    var fclosePtr = libc.findExportByName("fclose");

    var fopenFunc = new NativeFunction(fopenPtr, 'pointer', ['pointer', 'pointer']);
    var fgetsFunc = new NativeFunction(fgetsPtr, 'int', ['pointer', 'int', 'pointer']);
    var fcloseFunc = new NativeFunction(fclosePtr, 'int', ['pointer']);

    var pathPtr = Memory.allocUtf8String("/proc/self/cmdline");
    var openFlagsPtr = Memory.allocUtf8String("r");

    var fp = fopenFunc(pathPtr, openFlagsPtr);
    if(fp.isNull() === false){
        var buffData = Memory.alloc(128);
        var ret = fgetsFunc(buffData, 128, fp);
        if(ret !== 0){
            g_processName = ptr(buffData).readCString();
            logPrint("[*] ProcessName: " + g_processName);
        }
        fcloseFunc(fp);
    }
    return g_processName;
}

function arraybuffer2hexstr(buffer)
{
    var hexArr = Array.prototype.map.call(
        new Uint8Array(buffer),
        function (bit) {
            return ('00' + bit.toString(16)).slice(-2)
        }
    );
    return hexArr.join(' ');
}

function checkDexMagic(dataAddr){
    var magicMatch = true;
    var magicFlagHex = [0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00];

    for(var i = 0; i < 8; i++){
        if(ptr(dataAddr).add(i).readU8() !== magicFlagHex[i]){
            magicMatch = false;
            break;
        }
    }

    return magicMatch;
}

function checkOdexMagic(dataAddr){
    var magicMatch = true;
    var magicFlagHex = [0x64, 0x65, 0x79, 0x0a, 0x30, 0x33, 0x36, 0x00];

    for(var i = 0; i < 8; i++){
        if(ptr(dataAddr).add(i).readU8() !== magicFlagHex[i]){
            magicMatch = false;
            break;
        }
    }

    return magicMatch;
}

function dumpDexToFile(isDex, begin, g_processName) {
    //send(hexdump(begin, { offset: 0, header: false, length: 64, ansi: false }));
    var dexType;
    isDex ? dexType = "dex" : dexType = "odex";
    var magic = ptr(begin).readUtf8String().replace(/\n/g, '');
    var address = ptr(begin).add(isDex ? 0x20 : 0x1C);
    var dex_size = ptr(address).readInt();
    var dex_path = "/data/data/" + g_processName + "/" + dex_size + "." + dexType;
    var dex_file = new File(dex_path, "wb");

    dex_file.write(ptr(begin).readByteArray(dex_size));
    dex_file.flush();
    dex_file.close();

    logPrint("magic : " + magic );
    logPrint("size  : " + dex_size);
    logPrint("dumped " + dexType + " @ " + dex_path + "\n");
}

function dumpDex(moduleFuncName, g_processName){
    if(moduleFuncName !== ""){
        var hookFunction;
        if (g_AndroidOSVersion > 4) {
            hookFunction = Process.getModuleByName("libart.so").findExportByName(moduleFuncName);
        } else {
            hookFunction = Process.getModuleByName("libdvm.so").findExportByName(moduleFuncName);
            if(hookFunction == null) {
                hookFunction = Process.getModuleByName("libart.so").findExportByName(moduleFuncName);
            }
        }
        Interceptor.attach(hookFunction,{
            onEnter: function(args){
                var begin = 0;
                var dexMagicMatch = false;
                var odexMagicMatch = false;

                dexMagicMatch = checkDexMagic(args[0]);
                if (dexMagicMatch === true){
                    begin = args[0];
                }else {
                    odexMagicMatch = checkOdexMagic(args[0]);
                    if (odexMagicMatch === true) {
                        begin = args[0];
                    }
                }

                if (begin === 0){
                    dexMagicMatch = checkDexMagic(args[1]);
                    if(dexMagicMatch === true) {
                        begin = args[1];
                    }else{
                      odexMagicMatch = checkOdexMagic(args[1]);
                      if(odexMagicMatch === true) {
                          begin = args[1];
                      }
                    }
                }
                if (dexMagicMatch === true) {
                    dumpDexToFile(dexMagicMatch, begin, g_processName);
                } else if(odexMagicMatch === true) {
                    dumpDexToFile(odexMagicMatch, begin, g_processName);
                }
            },
            onLeave: function(retval) {
            }
        });
    }else{
	    logPrint("Error: cannot find correct module function.");
    }
}

// Main code
var g_AndroidOSVersion = getAndroidVersion();
var g_moduleFunctionName = getFunctionName();
var g_processName = getg_processName();

if(g_moduleFunctionName !== "" && g_processName !== ""){
    dumpDex(g_moduleFunctionName, g_processName);
}